﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Base;
using Silvermoon.Controls;
using Silvermoon.Animations;

namespace Silvermoon.UI
{
    [Flags]
    public enum TransitionMask
    {
        None = 0,
        Fade = 1,
        Explode = 2,
        Zoom = 4,
        Left = 8,
        Right = 16,
        Top = 32,
        Bottom = 64,
        RotateCenterY = 128,
        RotateTop = 256,
        RotateBottom = 512,
        RotateCenterX = 1024,
        RotateLeft = 2048,
        RotateRight = 4096,
        Bounce = 8192,
        RotateIn = 0x0,
        RotateOut = 0x4000,

        RotateLeftIn = RotateLeft | RotateIn,
        RotateRightIn = RotateRight | RotateIn,
        RotateLeftOut = RotateLeft | RotateOut,
        RotateRightOut = RotateRight | RotateOut,

        RotateCenterXOut = RotateCenterX | RotateOut,
        RotateCenterYIn = RotateCenterY | RotateIn,
        RotateCenterYOut = RotateCenterY | RotateOut,
    };

    /// <summary>
    /// Creates and sets <see cref="VisualStateManager"/> for forms, dialogs, tooltips and windows.
    /// </summary>
    public static class FormTransitions
    {
        private static readonly ScaleTransform explodeTransform = new ScaleTransform(8f, 8f, 1f);

        private static Transition[] GetTransitions(TransitionMask mask, bool show, int duration, int delay)
        {
            ValidateMode(mask, "mode");
            List<Transition> list = new List<Transition>();

            AddRotateVerticalIn(mask, show, duration, list);
            AddRotateHorizontalIn(mask, show, duration, list);
            AddRotateVerticalOut(mask, show, duration, list);
            AddRotateHorizontalOut(mask, show, duration, list);
            AddFade(mask, show, duration, list);
            AddZoomOrBounce(mask, show, duration, list);
            AddExplode(mask, show, duration, list);
            AddShiftY(mask, show, duration, list);
            AddShiftX(mask, show, duration, list);
            AddVisibility(show, duration, list);

            foreach (Transition t in list)
            {
                t.Tracking = TrackMode.Keep;
                t.Delay = delay;
            }
            return list.ToArray();
        }

        private static void AddRotateHorizontalOut(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.RotateOut) != 0)
            {
                TransitionMask bits = mask & (TransitionMask.RotateLeft | TransitionMask.RotateRight | TransitionMask.RotateCenterX);
                if (bits == 0) return;

                float start, end;
                switch (bits)
                {
                    case TransitionMask.RotateCenterX:
                        start = 0f;
                        end = -90f;
                        break;

                    case TransitionMask.RotateLeft:
                        start = -360f * 2f;
                        end = -360f * 2f - 85;
                        break;

                    case TransitionMask.RotateRight:
                        start = 360f * 2f;
                        end = 360f * 2f - 85;
                        break;

                    default:
                        throw new NotSupportedException();
                }

                FloatTransition translate = new FloatTransition
                {
                    Property = FormBase.RotateVerticalProperty,
                    Duration = duration,
                    //EasingFunction = new PowerEase { Mode = show ? EasingMode.EaseOut : EasingMode.EaseIn, Power = 2.5f }
                    //                    EasingFunction = !show ? CircleEase.In : CircleEase.Out
                    EasingFunction = LinearEase.Instance

                };
                if (show)
                {
                    translate.From = end;
                    translate.To = start;
                }
                else
                {
                    translate.From = start;
                    translate.To = end;
                }
                list.Add(translate);
            }
        }

        private static void AddRotateHorizontalIn(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.RotateOut) == 0)
            {

                TransitionMask bits = mask & (TransitionMask.RotateLeft | TransitionMask.RotateRight | TransitionMask.RotateCenterX);
                if (bits == 0) return;

                float start, end;
                switch (bits)
                {
                    case TransitionMask.RotateCenterX:
                        start = 0f;
                        end = 90f;
                        break;

                    case TransitionMask.RotateLeft:
                        start = -360f * 2f;
                        end = -360f * 2f + 105f;
                        break;

                    case TransitionMask.RotateRight:
                        start = 360f * 2f;
                        end = 360f * 2f + 105f;
                        break;

                    default:
                        throw new NotSupportedException();
                }

                FloatTransition translate = new FloatTransition
                {
                    Property = FormBase.RotateVerticalProperty,
                    Duration = duration,
                    //EasingFunction = show ? CircleEase.Out : CircleEase.In
                    EasingFunction = CircleEase.InOut

                    //EasingFunction = new PowerEase { Mode = show ? EasingMode.EaseOut : EasingMode.EaseIn, Power = 2.5f }

                };
                if (show)
                {
                    translate.From = end;
                    translate.To = start;
                }
                else
                {
                    translate.From = start;
                    translate.To = end;
                }
                list.Add(translate);
            }
        }

        private static void AddRotateVerticalOut(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.RotateOut) != 0)
            {

                TransitionMask bits = mask & (TransitionMask.RotateCenterY | TransitionMask.RotateTop | TransitionMask.RotateBottom);
                if (bits == 0) return;

                float start, end;
                switch (bits)
                {
                    case TransitionMask.RotateCenterY:
                        start = 0f;
                        end = -90f;
                        break;

                    case TransitionMask.RotateTop:
                        start = -360f * 2f;
                        end = -360f * 2f + 85f;
                        break;

                    case TransitionMask.RotateBottom:
                        start = 360f * 2f;
                        end = 360f * 2f - 85F;
                        break;

                    default:
                        throw new NotSupportedException();
                }

                FloatTransition translate = new FloatTransition
                {
                    Property = FormBase.RotateHorizonProperty,
                    Duration = duration,
                    EasingFunction = show ? CircleEase.In : CircleEase.Out
                };
                if (show)
                {
                    translate.From = end;
                    translate.To = start;
                }
                else
                {
                    translate.From = start;
                    translate.To = end;
                }
                list.Add(translate);
            }
        }

        private static void AddRotateVerticalIn(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.RotateOut) == 0)
            {

                TransitionMask bits = mask & (TransitionMask.RotateCenterY | TransitionMask.RotateTop | TransitionMask.RotateBottom);
                if (bits == 0) return;

                float start, end;
                switch (bits)
                {
                    case TransitionMask.RotateCenterY:
                        start = 0f;
                        end = 90f;
                        break;

                    case TransitionMask.RotateTop:
                        start = -360f * 2f;
                        end = -360f * 2f - 85F;
                        break;

                    case TransitionMask.RotateBottom:
                        start = 360f * 2f;
                        end = 360f * 2f + 85F;
                        break;

                    default:
                        throw new NotSupportedException();
                }

                IEasingFunction ease = GetVerticalRotationEase(show, bits);

                FloatTransition translate = new FloatTransition
                {
                    Property = FormBase.RotateHorizonProperty,
                    Duration = duration,
                    EasingFunction = ease
                };
                if (show)
                {
                    translate.From = end;
                    translate.To = start;
                }
                else
                {
                    translate.From = start;
                    translate.To = end;
                }
                list.Add(translate);
            }
        }

        private static IEasingFunction GetVerticalRotationEase(bool show, TransitionMask bits)
        {
            //         if ((bits & TransitionMask.RotateCenterY) != 0) 
            return !show ? CircleEase.In : CircleEase.Out;
            //            return show ? PowerEase.QuadraticOut : PowerEase.QuadraticIn;
        }

        private static void AddShiftX(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            var bits = mask & (TransitionMask.Left | TransitionMask.Right);
            if (bits == 0) return;
            bool right = (mask & TransitionMask.Right) != 0;

            FloatTransition translate = new FloatTransition
            {
                Property = FormBase.TranslateXFactorProperty,
                Duration = duration,
                EasingFunction = show ? PowerEase.Out : PowerEase.In
            };
            if (show)
            {
                translate.From = right ? 1f : -1f;
                translate.To = 0f;
            }
            else
            {
                translate.To = right ? 1f : -1f;
            }
            list.Add(translate);
        }

        private static void AddShiftY(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            var bits = mask & (TransitionMask.Bottom | TransitionMask.Top);
            if (bits == 0) return;
            bool bottom = (mask & TransitionMask.Bottom) != 0;

            FloatTransition translate = new FloatTransition { Property = FormBase.TranslateYFactorProperty, Duration = duration };
            if (show)
            {
                translate.From = bottom ? 1f : -1f;
                translate.To = 0f;
            }
            else
            {
                translate.To = bottom ? 1f : -1f;
            }
            list.Add(translate);
        }

        private static void AddVisibility(bool show, int duration, List<Transition> list)
        {
            FloatTransition visibility = new FloatTransition
            {
                Property = FormBase.VisibilityProperty,
                Duration = duration,
                EasingFunction = LinearEase.Instance
            };
            if (show)
            {
                visibility.From = 0.1f;
                visibility.To = 1f;
            }
            else
            {
                visibility.To = 0f;
            }
            list.Add(visibility);
        }

        private static void AddExplode(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.Explode) != 0)
            {
                ScaleTransition scale = new ScaleTransition { Property = Control.ScaleProperty, Duration = duration, EasingFunction = show ? PowerEase.QuadraticOut : PowerEase.QuadraticIn };
                if (show)
                {
                    scale.From = explodeTransform;
                    scale.To = ScaleTransform.One;
                }
                else
                {
                    scale.To = explodeTransform;
                }
                list.Add(scale);
            }
        }

        private static void AddZoomOrBounce(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & (TransitionMask.Zoom | TransitionMask.Bounce)) != 0)
            {
                ScaleTransition scale = new ScaleTransition { Property = Control.ScaleProperty, Duration = duration };
                if ((mask & TransitionMask.Zoom) != 0)
                {
                    //scale.EasingFunction = show ? PowerEase.Out : PowerEase.In;
                    scale.EasingFunction = show ? PowerEase.QuadraticOut : PowerEase.QuadraticIn;
                }
                else
                {
                    scale.EasingFunction = show ? BackEase.Out : BackEase.In;
                }
                if (show)
                {
                    scale.From = ScaleTransform.Zero2D;
                    scale.To = ScaleTransform.One;
                }
                else
                {
                    scale.To = ScaleTransform.Zero2D;
                }
                list.Add(scale);
            }
        }

        private static void AddFade(TransitionMask mask, bool show, int duration, List<Transition> list)
        {
            if ((mask & TransitionMask.Fade) != 0)
            {
                IntTransition fade = new IntTransition { Property = Control.OpacityProperty, Duration = duration };
                if (show)
                {
                    fade.EasingFunction = SineEase.Out;
                    fade.From = 0;
                    fade.To = 255;
                }
                else
                {
                    fade.EasingFunction = SineEase.In;
                    fade.To = 0;
                }
                list.Add(fade);
            }
        }

        public static void SetVisualStateManager(FormBase form, int duration, TransitionMask show, TransitionMask hide)
        {
            SetVisualStateManager(form, show, duration, hide, duration);
        }

        public static void SetVisualStateManager(FormBase form, TransitionMask show, int showDuration, TransitionMask hide, int hideDuration)
        {
            VisualStateManager manager = CreateVisualStateManager(show, showDuration, hide, hideDuration, 0, 0);
            form.TransitionManager = manager;
        }

        public static void SetVisualStateManager(FormBase form, TransitionMask show, int showDuration, int showDelay, TransitionMask hide, int hideDuration, int hideDelay)
        {
            VisualStateManager manager = CreateVisualStateManager(show, showDuration, hide, hideDuration, showDelay, hideDelay);
            form.TransitionManager = manager;
        }

        public static VisualStateManager CreateVisualStateManager(TransitionMask show, int showDuration, TransitionMask hide, int hideDuration, int showDelay, int hideDelay)
        {
            ValidateMode(show, "show");
            ValidateMode(hide, "hide");
            VisualState showState = new VisualState(VState.Shown);
            VisualState hideState = new VisualState(VState.Hidden);
            VisualStateGroup group = new VisualStateGroup(showState, hideState);

            showState.Transitions = GetTransitions(show, true, showDuration, showDelay);
            hideState.Transitions = GetTransitions(hide, false, hideDuration, hideDelay);

            VisualStateManager manager = new VisualStateManager(group);
            return manager;
        }

        private static void ValidateMode(TransitionMask mask, string paramName)
        {
            const TransitionMask cmp = TransitionMask.Explode | TransitionMask.Zoom | TransitionMask.Bounce;
            const TransitionMask cmp2 = TransitionMask.Bottom | TransitionMask.Top;
            const TransitionMask cmp3 = TransitionMask.RotateBottom | TransitionMask.RotateCenterY | TransitionMask.RotateTop;
            TransitionMask scale = mask & cmp;

            if (scale != 0 && scale != TransitionMask.Explode && scale != TransitionMask.Zoom && scale != TransitionMask.Bounce)
            {
                throw new ArgumentOutOfRangeException(paramName, "Explode, Bounce and Zoom are mutually exclusive.");
            }
            if ((mask & cmp2) == cmp2)
            {
                throw new ArgumentOutOfRangeException(paramName, "Bottom and Top are mutually exclusive.");
            }
            TransitionMask rot1 = mask & cmp3;
            if (rot1 != 0)
            {
                if (rot1 != TransitionMask.RotateBottom && rot1 != TransitionMask.RotateCenterY && rot1 != TransitionMask.RotateTop)
                {
                    throw new ArgumentOutOfRangeException(paramName, "RotateBottom, RotateTop and RotateCenterY are mutually exclusive.");
                }
            }
        }
    }
}
